/*
This file is part of IRCaBot.
IRCaBot is IRC logger with features.
Source code: https://notabug.org/acetone/ircabot.
Copyright (C) acetone, 2023.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "httpserversocket.h"
#include "dataparser.h"
#include "httpdocument.h"
#include "httpserver.h" // for constants
#include "httprequesttovoicegate.h"
#include "httprequesttochatlog.h"
#include "config.h"

#include <QDebug>

#define LOG_CRIT qCritical().noquote() << "[WebUI] HttpServerSocket:"
#define LOG_WARN qWarning().noquote() << "[WebUI] HttpServerSocket:"
#define LOG_INFO qInfo().noquote() << "[WebUI] HttpServerSocket:"
#define LOG_DEBUG qDebug().noquote() << "[WebUI] HttpServerSocket:"

constexpr const int VALUE_BEFORE_SPACE_MAX_SIZE = 300;
constexpr const int HEADERS_MAX_SIZE = 1000;

HttpServerSocket::HttpServerSocket(qintptr socketDescriptor, QObject *parent) :
    QObject(parent),
    m_socketDescriptor(socketDescriptor)
{

}

HttpServerSocket::~HttpServerSocket()
{
    if (not m_socket) return;

    if (m_socket->isOpen())
    {
        m_socket->close();
    }
    delete m_socket;
}

void HttpServerSocket::run()
{
    if (not m_socketDescriptor)
    {
        LOG_CRIT << "run() called with invalid socket descriptor";
        return;
    }

    m_socket = new QTcpSocket;
    m_socket->setSocketDescriptor(m_socketDescriptor);

    if (m_socket->waitForReadyRead())
    {
        reader();

        if (not m_data.isEmpty())
        {
            m_socket->write(m_data);
            while (m_socket->bytesToWrite() > 0)
            {
                if (m_socket->state() == QTcpSocket::UnconnectedState)
                {
                    LOG_WARN << "Turned to unconnected state at writing moment. Peer:" << m_socket->peerAddress().toString();
                    break;
                }
                m_socket->waitForBytesWritten(100);
            }
        }
        else
        {
            LOG_WARN << "Data to write is empty";
        }
    }
    else
    {
        LOG_WARN << "Reading timed out. Peer:" << m_socket->peerAddress().toString();
    }
}

void HttpServerSocket::reader()
{
    const QString reqType = readBeforeSpace(4);
    if (reqType != "GET" and reqType != "POST")
    {
        m_data = "HTTP/1.0 405 Method Not Allowed\r\n"
                 "Allow: GET, POST\r\n\r\n";
        return;
    }

    bool tooLong = false;
    QString urlPath = QByteArray::fromPercentEncoding( readBeforeSpace(0, &tooLong).toUtf8() );

    if (urlPath.contains(".."))
    {
        m_data = "HTTP/1.0 409 Conflict\r\n\r\n";
        return;
    }
    if (tooLong)
    {
        m_data = "HTTP/1.0 414 URI Too Long\r\n\r\n";
        return;
    }

    QString headers = readBeforeBody(0, &tooLong);
    if (tooLong)
    {
        m_data = "HTTP/1.0 431 Request Header Fields Too Large\r\n\r\n";
        return;
    }

    if (reqType == "GET")
    {
        onGET(urlPath, headers);
    }
    else
    {
        QString body;
        body = m_socket->readAll();
        onPOST(urlPath, headers, body);
    }
}

void HttpServerSocket::onGET(QString &url, QString &headers)
{
    if (url == "/")
    {
        // Yeah, baby, I'm crazy and IRCaBot works with *.php URLs without PHP
        m_data = "HTTP/1.0 301 Moved Permanently\r\n"
                 "Location: /"+QString(HTTP_URI_MAIN_PREFIX).toUtf8()+"\r\n\r\n";
        return;
    }

    url.remove(0,1); // remove '/'
    if (url == "favicon.ico")
    {
        url = HTTP_URI_STATIC_PREFIX+QString("/favicon.ico");
    }

    if (url == HTTP_URI_MAIN_PREFIX)
    {
        HttpDocument doc;
        doc.setBodyStandardError("Main page not implemented yet", "Hello world", true);
        m_data = doc.document();
    }
    else if (url.startsWith(HTTP_URI_CHATLOG_PREFIX))
    {
        m_data = HttpRequestToChatLog(url).document();
    }
    else if (url.startsWith(HTTP_URI_VOICEGATE_PREFIX))
    {
        m_data = HttpRequestToVoiceGate(url).document();
    }
    else if (url.startsWith(HTTP_URI_STATIC_EMBEDDED_PREFIX))
    {
        static const QRegularExpression staticPrefix("^static.php");
        url.remove(staticPrefix);
        const QString eTag = parser::get(headers, "If-None-Match", parser::GetType::HttpHeader);
        HttpDocument document;
        document.setBody(QFile(":/public/html" + url), eTag);
        m_data = document.document();
    }
    else if (url.startsWith(HTTP_URI_STATIC_PREFIX))
    {
        static const QRegularExpression staticPrefix("^static");
        url.remove(staticPrefix);
        const QString eTag = parser::get(headers, "If-None-Match", parser::GetType::HttpHeader);
        HttpDocument document;
        document.setBody(QFile(Config::workingDirectory() + "html" + url), eTag);
        m_data = document.document();
    }
    else
    {
        HttpDocument doc;
        doc.setCode(http::CODE_400);
        doc.setBodyStandardError("Unknown URL requested", "Bad request");
        m_data = doc.document();
    }
}

void HttpServerSocket::onPOST(QString &url, QString &header, QString &body)
{
    // for real time reading and serach (JS designed features)
}

QString HttpServerSocket::readBeforeSpace(int maxLength, bool * limited)
{
    if (maxLength < 1)
    {
        maxLength = VALUE_BEFORE_SPACE_MAX_SIZE;
    }

    QString result;
    char symbol {0};
    qint64 tail = m_socket->read(&symbol, 1);
    while (symbol != ' ' and tail > 0)
    {
        if (result.size() > maxLength)
        {
            if (limited != nullptr)
            {
                *limited = true;
            }
            break;
        }
        result += symbol;
        tail = m_socket->read(&symbol, 1);
    }
    return result;
}

QString HttpServerSocket::readBeforeBody(int maxLength, bool *limited)
{
    if (maxLength < 1)
    {
        maxLength = HEADERS_MAX_SIZE;
    }

    QString result;
    char symbol {0};
    qint64 tail = m_socket->read(&symbol, 1);
    while (not result.endsWith("\r\n\r") and tail > 0)
    {
        if (result.size() > maxLength)
        {
            if (limited != nullptr)
            {
                *limited = true;
            }
            break;
        }
        result += symbol;
        tail = m_socket->read(&symbol, 1);
    }
    return result;
}

